# -*- coding: utf-8 -*-
#
#  Copyright (C) 2012 by Ihor E. Novikov
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Affero General Public License
#  as published by the Free Software Foundation, either version 3
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU Affero General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import os, sys
import traceback

import zipfile
from zipfile import ZipFile

from uc2 import events, msgconst
from uc2.utils import get_chunk_size, dword2py_int, py_int2dword
from uc2.formats.riff import model
from uc2.formats.cdrz.model import generic_dict


class CDRZ_Loader:
    name = 'CDRZ_Loader'
    version = 'CDRF'
    obj_map = {}

    def __init__(self):
        pass

    def load(self, presenter, path):
        self.presenter = presenter
        self.path = path

        if not zipfile.is_zipfile(self.path):
            msg = _('It seems the file is not CDRZ file')
            events.emit(events.MESSAGES, msgconst.ERROR, msg)
            raise IOError(2, msg)

        self._extract_content()
        self.model = None

        path = os.path.join(self.presenter.doc_dir, 'content', 'riffData.cdr')

        #		try:
        #			file = open(path, 'rb')
        #		except:
        #			errtype, value, trace = sys.exc_info()
        #			msg = _('Cannot open %s file for reading') % (path)
        #			events.emit(events.MESSAGES, msgconst.ERROR, msg)
        #			raise IOError(errtype, msg + '\n' + value, trace)
        #
        #		self.model = self.parse_file(file)
        #
        #		file.close()
        return self.model

    def _extract_content(self):
        pdxf_file = ZipFile(self.path, 'r')
        try:
            fl = pdxf_file.namelist()
        except:
            errtype, value, traceback = sys.exc_info()
            msg = _('It seems the CDR file is corrupted') + '\n' + value
            events.emit(events.MESSAGES, msgconst.ERROR, msg)
            raise IOError(errtype, msg, traceback)

        filelist = []
        for item in fl:
            if item[-1] == '/':
                os.makedirs(self.path)
            filelist.append(item)

        for item in filelist:
            source = pdxf_file.read(item)
            dest = open(os.path.join(self.presenter.doc_dir, item), 'wb')
            dest.write(source)
            dest.close()
        msg = _('The file content is extracted successfully')
        events.emit(events.MESSAGES, msgconst.OK, msg)

    def parse_file(self, file):
        identifier = file.read(4)
        size_field = file.read(4)
        list_identifier = file.read(4)
        self.version = list_identifier
        self.obj_map = generic_dict
        obj = model.RiffRootList(identifier + size_field + list_identifier)

        size = get_chunk_size(size_field)
        while file.tell() < size + 8:
            ret = self.parse_stream(file)
            obj.childs.append(ret)

        return obj

    def get_class(self, identifier, list_identifier=''):
        if list_identifier:
            if self.obj_map.has_key(list_identifier):
                return self.obj_map[list_identifier]
            else:
                return model.RiffList
        else:
            if self.obj_map.has_key(identifier):
                return self.obj_map[identifier]
            else:
                return model.RiffObject

    def parse_stream(self, file):
        identifier = file.read(4)
        if identifier == 'LIST':
            return self.parse_list(file, identifier)
        else:
            return self.parse_object(file, identifier)

    def parse_list(self, file, identifier):
        size_field = file.read(4)
        list_identifier = file.read(4)

        size = get_chunk_size(size_field)
        offset = file.tell()

        if list_identifier == 'cmpr':
            file.seek(offset)
            chunk = file.read(size - 4)
            return self.parse_cmpr_list(identifier + size_field + \
                                        list_identifier + chunk)

        class_ = self.get_class(identifier, list_identifier)
        obj = class_(identifier + size_field + list_identifier)

        while file.tell() <= offset + size - 8:
            ret = self.parse_stream(file)
            if ret is None:
                file.seek(offset)
                chunk = file.read(size - 4)
                return model.RiffUnparsedList(identifier + size_field + \
                                              list_identifier + chunk)
            else:
                obj.childs.append(ret)

        return obj

    def parse_object(self, file, identifier):
        if not identifier[:3].isalnum():
            return None
        size_field = file.read(4)
        size = get_chunk_size(size_field)
        chunk = file.read(size)
        class_ = self.get_class(identifier)
        return class_(identifier + size_field + chunk)

    def parse_cmpr_list(self, buffer):
        obj = model.RiffCmprList(buffer)
        import StringIO, zlib
        compressedsize = dword2py_int(buffer[12:16])

        decomp = zlib.decompressobj()
        uncompresseddata = decomp.decompress(buffer[36:])

        blocksizesdata = zlib.decompress(buffer[36 + compressedsize:])
        blocksizes = []
        for i in range(0, len(blocksizesdata), 4):
            blocksizes.append(dword2py_int(blocksizesdata[i:i + 4]))

        stream = StringIO.StringIO(uncompresseddata)
        while stream.tell() < len(uncompresseddata):
            ret = self.parse_comressed_stream(stream, blocksizes)
            obj.childs.append(ret)

        return obj

    def parse_comressed_stream(self, stream, blocksizes):
        identifier = stream.read(4)
        if identifier == 'LIST':
            return self.parse_compressed_list(stream, identifier, blocksizes)
        else:
            return self.parse_compressed_object(stream, identifier, blocksizes)

    def parse_compressed_list(self, stream, identifier, blocksizes):
        rawsize = dword2py_int(stream.read(4))
        list_identifier = stream.read(4)
        size = blocksizes[rawsize]

        size_field = py_int2dword(size)
        if size & 1: size += 1

        offset = stream.tell()

        class_ = self.get_class(identifier, list_identifier)
        obj = class_(identifier + size_field + list_identifier)

        while stream.tell() <= offset + size - 8:
            ret = self.parse_comressed_stream(stream, blocksizes)
            if ret is None:
                stream.seek(offset)
                chunk = stream.read(size - 4)
                return model.RiffUnparsedList(identifier + size_field + \
                                              list_identifier + chunk)
            else:
                obj.childs.append(ret)

        return obj

    def parse_compressed_object(self, stream, identifier, blocksizes):
        if not identifier[:3].isalnum():
            return None
        rawsize = dword2py_int(stream.read(4))
        size = blocksizes[rawsize]
        size_field = py_int2dword(size)
        if size & 1: size += 1
        chunk = stream.read(size)
        class_ = self.get_class(identifier)
        return class_(identifier + size_field + chunk)


class CDRZ_Saver:
    name = 'CDRZ_Saver'

    def __init__(self):
        pass

    def save(self, presenter, path):

        try:
            file = open(path, 'wb')
        except:
            errtype, value, traceback = sys.exc_info()
            msg = _('Cannot open %s file for writing') % (path)
            events.emit(events.MESSAGES, msgconst.ERROR, msg)
            raise IOError(errtype, msg + '\n' + value, traceback)

        file.write(presenter.model.get_chunk())
        file.close()
